// Bristleback plugin - Copyright (c) 2010 bristleback.googlecode.com
// ---------------------------------------------------------------------------
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by the
// Free Software Foundation; either version 3 of the License, or (at your
// option) any later version.
// This library is distributed in the hope that it will be useful,
// but without any warranty; without even the implied warranty of merchantability
// or fitness for a particular purpose.
// You should have received a copy of the GNU Lesser General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/lgpl.html>.
// ---------------------------------------------------------------------------
package pl.bristleback.server.bristle.config;

import org.apache.log4j.Logger;
import pl.bristleback.server.bristle.actions.AnnotatedRemoteAction;
import pl.bristleback.server.bristle.actions.RemoteAction;
import pl.bristleback.server.bristle.actions.RemoteActionInformation;
import pl.bristleback.server.bristle.binding.BindingsProcessor;
import pl.bristleback.server.bristle.binding.ObjectBinder;
import pl.bristleback.server.bristle.exceptions.handlers.MissingRightsErrorHandler;
import pl.bristleback.server.bristle.token.Tokenizer;
import pl.bristleback.server.bristle.token.TokenizerCreator;
import pl.bristleback.server.bristle.utils.ActionClassElement;
import pl.bristleback.server.bristle.utils.ReflectionUtil;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Class used to resolve and bind information about annotated action class.
 * Provides check of annotation appearance in action class.
 * In addition to binding basic information about action class,
 * tokenizer and binder fields occurring in processed action class will be resolved and initialized using annotations.
 * See {@link pl.bristleback.server.bristle.token.Tokenizer} and {@link pl.bristleback.server.bristle.binding.ObjectBinder}
 * classes for more information. <br>
 * It is a helper class for {@link ActionAnnotationsProcessor} implementations,
 * but it can be helpful in any other implementations.
 * Note that this class does not create any remote action instance,
 * it is eventually (if action class is set to be singleton) done by {@link pl.bristleback.server.bristle.actions.ActionsInitializer} class.
 * <p/>
 * Created on: 2010-09-03 16:04:52 <br/>
 *
 * @author Wojciech Niemiec
 */
public final class ActionInformationResolver {
  private static Logger log = Logger.getLogger(ActionInformationResolver.class.getName());

  /**
   * Gets action information wrapper for given action class.
   *
   * @param actionClass   remote action class.
   * @param configuration Bristleback plugin configuration.
   * @return action information wrapper if everything is fine,
   *         null if action class is not annotated, exception if instance of action class cannot be created.
   */
  public RemoteActionInformation getActionInformation(Class<? extends RemoteAction> actionClass, BristleConfiguration configuration) {
    AnnotatedRemoteAction actionAnnotation = actionClass.getAnnotation(AnnotatedRemoteAction.class);
    if (actionAnnotation == null) {
      if (log.isDebugEnabled()) {
        log.debug("Cannot create action information object from class " + actionClass + ", "
          + "action class is not signed with " + AnnotatedRemoteAction.class.getSimpleName() + " annotation.");
      }
      return null;
    }
    RemoteActionInformation actionInformation = createActionInformationObject(actionClass, actionAnnotation);
    getTokenizerWrappers(actionInformation, configuration);
    getBinders(actionInformation, configuration);
    return actionInformation;
  }

  private RemoteActionInformation createActionInformationObject(Class<? extends RemoteAction> actionClass, AnnotatedRemoteAction actionAnnotation) {
    RemoteActionInformation actionInformation = new RemoteActionInformation();
    actionInformation.setActionClass(actionClass);
    actionInformation.setActionName(getActionName(actionAnnotation));
    actionInformation.setRequiredRights(getActionRequiredRights(actionAnnotation));
    boolean actionHandlingMissingRightsError = isActionHandlingMissingRightsError(actionClass);
    actionInformation.setActionHandlingErrors(actionHandlingMissingRightsError);
    actionInformation.setSingleton(isActionSingleton(actionAnnotation));

    return actionInformation;
  }

  private void getTokenizerWrappers(RemoteActionInformation actionInformation, BristleConfiguration configuration) {
    TokenizerCreator tokenizerCreator = new TokenizerCreator(actionInformation.getActionClass());

    List<Field> tokenizerFields = ReflectionUtil.getDependencyFieldsWithType(actionInformation.getActionClass(), Tokenizer.class, true);
    List<ActionClassElement> tokenizerWrappers = new ArrayList<ActionClassElement>();
    for (Field tokenizerField : tokenizerFields) {
      tokenizerCreator.setTokenizerField(tokenizerField);
      tokenizerWrappers.add(tokenizerCreator.createTokenizer(configuration));
    }
    actionInformation.setTokenizerWrappers(tokenizerWrappers);
  }

  private void getBinders(RemoteActionInformation actionInformation, BristleConfiguration configuration) {
    BindingsProcessor bindingsProcessor = configuration.getBindingsProcessor();
    List<ActionClassElement<ObjectBinder>> binders = bindingsProcessor.processBindings(actionInformation.getActionClass(), configuration);
    actionInformation.setBinders(binders);
  }

  private boolean isActionHandlingMissingRightsError(Class<? extends RemoteAction> actionClass) {
    return ReflectionUtil.hasInterface(actionClass, MissingRightsErrorHandler.class);
  }

  private String getActionName(AnnotatedRemoteAction actionAnnotation) {
    return actionAnnotation.actionName();
  }

  private Set<String> getActionRequiredRights(AnnotatedRemoteAction actionAnnotation) {
    String[] rightsArray = actionAnnotation.requiredRights();
    return new HashSet<String>(Arrays.asList(rightsArray));
  }

  private boolean isActionSingleton(AnnotatedRemoteAction actionAnnotation) {
    return actionAnnotation.isSingleton();
  }
}
